/*
 * Copyright 2011 group coding kata nantes
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package fr.coding.kata.nantes.utils.file;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.omg.CORBA.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.URL;
import java.util.List;

/**
 * Utility class to access files.
 *
 * @author Sylvain Leduby
 * @since 0.1
 */
public final class FileUtil {

    /**
     * Logger.
     */
    private static final Logger LOG = LoggerFactory.getLogger(FileUtil.class);

    /**
     * UNIX file separator.
     */
    public static final char UNIX_SEPARATOR = '/';

    /**
     * Windows file separator.
     */
    public static final char WINDOWS_SEPARATOR = '\\';

    /**
     * Private constructor.
     */
    private FileUtil() {
        super();
    }

    /**
     * Tells if character is a separator.
     *
     * @param character A character.
     * @return <code>true</code> if the character is a separator.
     */
    private static boolean isSeparator(final char character) {
        return character == UNIX_SEPARATOR || character == WINDOWS_SEPARATOR;
    }

    /**
     * Load a file to a String.
     *
     * @param path The path of the file to load.
     * @return The content of the file with system line separator.
     * @throws IOException If an error occurs when reading the file.
     */
    public static String load(final String path) throws IOException {
        return load(path, IOUtils.LINE_SEPARATOR);
    }

    /**
     * Load a file to a String.
     *
     * @param path          The path of the file to load.
     * @param lineSeparator The line separator to use.
     * @return The content of the file with given line separator.
     * @throws IOException If an error occurs when reading the file.
     */
    public static String load(final String path, final String lineSeparator) throws IOException {
        LOG.debug("Enter method: load");

        // read expected result form file
        final InputStream stream = getResourceAsStream(path);

        List<String> lines = null;
        try {
            lines = IOUtils.readLines(stream, "UTF-8");
        } finally {
            IOUtils.closeQuietly(stream);
        }

        // fix line separator
        final StringBuilder buffer = new StringBuilder(1000);
        for (final Object element : lines) {
            buffer.append(element).append(lineSeparator);
        }

        LOG.debug("End method: load");

        return buffer.toString();
    }

    /**
     * Get a {@link File} from given path.
     *
     * @param path The path of the file.
     * @return File The file for the path.
     * @throws FileNotFoundException If the file was not found.
     */
    public static File find(final String path) throws FileNotFoundException {
        LOG.debug("Enter method: find");

        File theFile = null;
        if (StringUtils.isNotBlank(path)) {
            // clean the path
            final String cleanPath = clean(path);

            // tries to get file directly
            theFile = new File(cleanPath);
            if (!theFile.exists()) {
                LOG.debug("The file was not found with direct access");

                // get URL for path
                URL resource = null;
                final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                if (classLoader != null) {
                    LOG.debug("Try with current thread's ClassLoader");
                    resource = classLoader.getResource(cleanPath);
                }

                if (resource == null) {
                    LOG.debug("Try with Environment");
                    resource = Environment.class.getResource(cleanPath);
                }

                if (resource == null && Environment.class.getClassLoader() != null) {
                    LOG.debug("Try with ClassLoader of Environment");
                    resource = Environment.class.getClassLoader().getResource(cleanPath);
                }

                if (resource == null) {
                    LOG.debug("Try with FileUtil");
                    resource = FileUtil.class.getResource(cleanPath);
                }

                if (resource == null) {
                    final String msg = "No resource found for: " + cleanPath;
                    LOG.info(msg);
                    throw new FileNotFoundException(msg);
                }

                theFile = getFile(resource.getFile());
                if (theFile == null || !theFile.exists()) {
                    final String msg = "No resource found for: " + resource.getFile();
                    LOG.info(msg);
                    throw new FileNotFoundException(msg);
                }

                LOG.info("File found at: {}", theFile.getAbsolutePath());
            }
        }

        LOG.debug("End method: find");

        return theFile;
    }

    private static File getFile(final String url) {
        LOG.debug("Enter method: getFile({})", url);

        File theFile = null;
        if (StringUtils.isNotBlank(url)) {
            theFile = new File(url);
            if (!theFile.exists()) {
                // Protocol issue?
                final String[] splitted = url.split(":");
                if (splitted.length == 3) {
                    // remove protocol
                    theFile = getFile(splitted[1] + ":" + splitted[2]);
                }
            }
        }

        LOG.debug("End method: getFile");

        return theFile;
    }

    /**
     * Try to get resource form provided string.
     *
     * @param path The path of the resource.
     * @return InputStream for provided resource.
     * @throws FileNotFoundException If the file was not found.
     */
    public static InputStream getResourceAsStream(final String path) throws FileNotFoundException {
        LOG.debug("Enter method: getResourceAsStream");

        InputStream stream = null;

        if (StringUtils.isNotBlank(path)) {
            final String stripped = isSeparator(path.charAt(0)) ? path.substring(1) : path;

            if (StringUtils.isNotBlank(stripped)) {
                final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                if (classLoader != null) {
                    LOG.debug("Try with current thread's ClassLoader");
                    stream = classLoader.getResourceAsStream(stripped);
                }

                if (stream == null) {
                    LOG.debug("Try with Environment");
                    stream = Environment.class.getResourceAsStream(path);
                }

                if (stream == null && Environment.class.getClassLoader() != null) {
                    LOG.debug("Try with ClassLoader of Environment");
                    stream = Environment.class.getClassLoader().getResourceAsStream(stripped);
                }

                if (stream == null) {
                    final File fichier = find(stripped);
                    stream = new FileInputStream(fichier);
                }
            }

            if (stream == null) {
                final String msg = "No resource found for: " + path;
                LOG.info(msg);
                throw new FileNotFoundException(msg);
            }
        }

        LOG.debug("End method: getResourceAsStream");

        return stream;
    }

    /**
     * Clean a path.
     * <p>
     * Remplace '\' with '/'.<br />
     * Add a separator ('\' or '/') at end if needed.
     * </p>
     *
     * @param path The path to clean.
     * @return Cleaned path.
     */
    public static String clean(final String path) {
        LOG.debug("Enter method: clean");

        String cleanPath = path;
        if (StringUtils.isNotBlank(cleanPath)) {
            int size = cleanPath.length();
            final char[] array = new char[size + 2]; // +1 for ending separator,
            // +2 pour le copie
            cleanPath.getChars(0, size, array, 0);

            // WINDOWS_SEPARATOR to UNIX_SEPARATOR
            for (int i = 0; i < array.length; i++) {
                if (array[i] == WINDOWS_SEPARATOR) {
                    array[i] = UNIX_SEPARATOR;
                }
            }

            // ensure the path ends with a separator
            final boolean addEndSeparator = array[size - 1] != UNIX_SEPARATOR;
            if (addEndSeparator) {
                LOG.debug("Add ending seperator");
                array[size++] = UNIX_SEPARATOR;
            }

            int start = cleanPath.indexOf("://");
            if (start == -1) {
                start = 0;
            } else {
                start += 3;
            }

            // merge double separators
            for (int i = start + 1; i < size; i++) {
                if (array[i] == UNIX_SEPARATOR && array[i - 1] == UNIX_SEPARATOR) {
                    System.arraycopy(array, i, array, i - 1, size - i);
                    size--;
                    i--;
                }
            }

            if (addEndSeparator) {
                cleanPath = new String(array, 0, size - 1);
            } else {
                cleanPath = new String(array, 0, size);
            }
        }

        LOG.debug("End method: clean");

        return cleanPath;
    }

    /**
     * Write given {@code content} to disk in a file located at {@code path}.
     *
     * @param path    Path of the file to store content.
     * @param content Content of the file.
     * @throws IOException If an error occurs when writing the file.
     */
    public static void write(final String path, final String content) throws IOException {
        write(path, content, "UTF-8");
    }

    /**
     * Write given {@code content} to disk in a file located at {@code path}.
     *
     * @param filename Complete path of the file to store content.
     * @param content  Content of the file.
     * @param encoding File encoding to use.
     * @throws IOException If an error occurs when writing the file.
     */
    public static void write(final String filename, final String content, final String encoding)
            throws IOException {
        LOG.debug("Enter method: write");

        Writer writer = null;
        try {
            // create folders hierarchy if needed
            final String folder = FilenameUtils.getPath(filename);
            if (new File(folder).mkdirs()) {
                // creates writer
                writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename, false),
                        encoding));
                writer.write(content);
            }
        } finally {
            IOUtils.closeQuietly(writer);
        }

        LOG.debug("End method: write");
    }

}
